;(function(factory) {
	if(typeof define === 'function' && define.amd) {
		define(['jquery', 'frame/XFrame_Graph', 'frame/BaseShape', 'frame/Tween', 'frame/AnimationHelper'], factory);
	}else {
		factory(jQuery, XFrame_Graph, BaseShape, Tween, AnimationHelper,window);
	}
}(function($, XFrame_Graph, BaseShape, Tween, AH, global) {
	'use strict';
	var _baseShape = null, FX = null, Effects = null, QueueManager = {};

    Array.prototype.indexOf || (Array.prototype.indexOf = function(item, index) {
        var n = this.length,
            i = ~~index;
        if (i < 0)
            i += n;
        for (; i < n; i++)
            if (this[i] === item)
                return i;
        return -1;
    });

	_baseShape = BaseShape.prototype;

    _baseShape._q = {};

    _baseShape.queue = function(type, fn){
        if($.isFunction(type)){
            fn = type;
            type = null;
        }
        QueueManager.queue(this, type, fn);
        return this;
    };

    _baseShape.dequeue = function(type){
        QueueManager.dequeue(this, type);
        return this;
    };

    QueueManager.queue = function(bs, type, data){
        var queueData = null;
        type = type || 'fx';
        if(!bs || !bs.instanceOf || !bs.instanceOf(BaseShape)){
            return;
        }

        queueData = bs._q[type];

        if(data){
            if(!queueData || $.isArray(data)){
                queueData = bs._q[type] = $.makeArray(data);
            }else{
                queueData.push(data);
            }

            if(queueData[0] !== 'fxRunning'){
                QueueManager.dequeue(bs, type);
            }

        }else{
            return queueData || [];
        }
    };

    QueueManager.dequeue = function(bs, type){
        var queueData = QueueManager.queue(bs, type), fn = queueData.shift();
        type = type || 'fx';

        if(fn === 'fxRunning'){
            fn = queueData.shift();
        }

        if(fn){
            if(type === 'fx'){
                queueData.unshift('fxRunning');
            }
            fn.call(bs, markFunction(function(){
                QueueManager.dequeue(bs, type);
            }));
        }

    };

    QueueManager.clearQueue = function(bs, type){
        QueueManager.queue(bs, type, []);
    };

    function markFunction(fn){
        fn.dequeueFn = true;
        return fn;
    }

	FX = XFrame_Graph._createClass({
		init : function(elem, conf){
			this.elem = elem;
			this.conf = conf || {};
            this.attrName = this.conf.attrName;
			//this.state = this.conf.state || 0;	//0:not started,1: running,2:stopped
			this.startTime = 0;
            this.timeLength = this.conf.timeLength || 1500;
            this.tFn = Tween.getTween(this.conf.type, this.conf.mode);
            this.startValue = this.conf.startValue;
            this.endValue = this.conf.endValue;
            this.changeDiff = this.endValue - this.startValue;
            this.nowValue = 0;
            this._d = $.Deferred();

            if(this.conf.autoStart){
                this.start();
            }
		},
        step : function(){
            var duringTime = $.now() - this.startTime;
            this.nowValue = this.tFn(duringTime, this.startValue, this.changeDiff, this.timeLength);

            if(duringTime >= this.timeLength){
                this.finish();
            }else{
                this.elem[this.attrName] = this.nowValue;
            }
        },

        start : function(){
            this.startTime = $.now();
            FX.allEf.push(this);
            FX.tick();
        },

        finish : function(){
            this.elem[this.attrName] = this.endValue;
            FX.allEf.splice(FX.allEf.indexOf(this), 1);
            this._d.resolve();
            XFrame_Graph.refresh();
        },

        getPromise : function(){
            return this._d.promise();
        }
	});

	FX.allEf = [];

    FX.timmerID = null;

    FX.tick = function(){
        if(FX.timmerID){
            return;
        }
        var processFrame = function(){
            var fxElems = {};
            if(!FX.allEf.length){
                FX.stop();
                return;
            }
            $.each(FX.allEf, function(idx, fx){
                if(!fx){
                    return;
                }
                fxElems[fx.elem.uuid] = fx.elem;
                fx.step();
            });
            $.each(fxElems, function(_, elem){
                elem.updateCommands();
            });
            XFrame_Graph.refresh();
            FX.timerID = AH.requestAnimationFrame(processFrame);
        };
        FX.timerID = AH.requestAnimationFrame(processFrame);
    };

    FX.stop = function(){
        AH.cancelAnimationFrame(FX.timerID);
        FX.timerID = null;
    };

    function makeAnimationFxs(elem, targetEndProps, fxMode, speed, manualStart){
        var thisAnimeFxs = [], fm = (fxMode || '').split('-');
        speed = speed || 1500;
        switch (speed){
            case 'fast' :
                speed = 500;
                break;
            case 'normal' :
                speed = 1500;
                break;
            case 'slow' :
                speed = 3000;
                break;
            default :
                speed = +speed;
                speed = speed || 1500;
        }
        $.each(targetEndProps, function(propName, endValue){
            var fxObj = new FX(elem, {
                attrName : propName,
                timeLength : speed,
                type : fm[0],
                mode : fm[1],
                startValue : elem[propName],
                endValue : endValue,
                autoStart : !manualStart
            });
            thisAnimeFxs.push(fxObj);
        });

        return thisAnimeFxs;
    }

	Effects = {
		registAnimation : function(elem, targetEndProps, fxMode, speed, manualStart){
            var animationDeferred = $.Deferred();
            elem.queue(function(next){
                var allPromises, thisAnimeFxs = makeAnimationFxs(elem, targetEndProps, fxMode, speed, manualStart);
                elem._nowAnimeFxs = thisAnimeFxs;

                allPromises = $.map(thisAnimeFxs, function(v){return v.getPromise();});
                $.when.apply(null, allPromises).done(function(){
                    animationDeferred.resolve();
                    elem._nowAnimeFxs = null;
                    next && next.dequeueFn && next();
                });
            });

            return animationDeferred.promise();
		},
		disableEf : function(){

		},
        _FX : FX
	};

    _baseShape.animeParallel = function(animationParams, fxMode, speed){
        var thisShape = this, animationDeferred = $.Deferred();
        this.queue(function(next){
            var allPromises, thisAnimeFxs = [];
            $.each(animationParams, function(_, param){
                thisAnimeFxs.push.apply(thisAnimeFxs, makeAnimationFxs(thisShape, param[0], param[1] || fxMode, param[2] || speed));
            });

            thisShape._nowAnimeFxs = thisAnimeFxs;

            allPromises = $.map(thisAnimeFxs, function(v){return v.getPromise();});
            $.when.apply(null, allPromises).done(function(){
                animationDeferred.resolve();
                thisShape._nowAnimeFxs = null;
                next && next.dequeueFn && next();
            });
        });

        this['_lastPromise'] = animationDeferred.promise();
        return this;
    };

    _baseShape.animation = function(targetEndProps, fxMode, speed){
        this['_lastPromise'] = Effects.registAnimation(this, targetEndProps, fxMode, speed);
        return this;
    };

    _baseShape.animeDelay = function(time){
        var delayDeferred = $.Deferred();
        this.queue(function(next){
            setTimeout(function(){
                delayDeferred.resolve();
                next && next.dequeueFn && next();
            }, time);
        });

        this['_lastPromise'] = delayDeferred.promise();
        return this;
    };

    _baseShape.promise = function(){
        var p = this['_lastPromise'], d;
        if(!p){
            d = $.Deferred();
            d.resolve();
            p = d.promise();
        }
        return p;
    };

	global && (global.Effects = Effects);
    (typeof debugContext !== 'undefined' && debugContext && (debugContext['Effects'] = Effects));
	return Effects;
}));